package net.cyue.web.easyquery.core.db.util;

import net.cyue.util.ReflectUtil;
import org.apache.commons.text.StringEscapeUtils;

import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * SQL 工具类
 * <p>
 * 提供SQL处理相关功能，包括：
 * - SQL预编译参数处理（#{}格式）
 * - 直接替换参数处理（${}格式）
 * - 参数名提取和值映射
 * - XSS攻击防护
 * - SQL字符串拼接和转义
 * - 数组和集合类型的特殊处理
 * </p>
 */
public class SQLUtil {

    // 匹配 #{parameter:default} 格式的参数（支持默认值）
    private static final Pattern PARAM_PATTERN = Pattern.compile("#\\{([^:}]+)(?::([^}]+))?}");

    // 匹配 ${parameter:default} 格式的参数（支持默认值）
    private static final Pattern DOLLAR_PARAM_PATTERN = Pattern.compile("\\$\\{([^:}]+)(?::([^}]+))?}");

    /**
     * 用于存储预编译SQL和参数名的数据结构
     */
    public static class SqlParams {
        private final String sql;
        private final List<String> paramNames;
        private final Map<String, String> paramDefaults; // 存储参数的默认值

        /**
         * 构造函数
         *
         * @param sql 预编译SQL
         * @param paramNames 参数名列表
         * @param paramDefaults 参数默认值
         */
        public SqlParams(String sql, List<String> paramNames, Map<String, String> paramDefaults) {
            this.sql = sql;
            this.paramNames = paramNames;
            this.paramDefaults = paramDefaults;
        }

        /**
         * 获取预编译SQL
         *
         * @return 预编译SQL
         */
        public String getSql() {
            return sql;
        }

        /**
         * 获取参数名列表
         *
         * @return 参数名列表
         */
        public List<String> getParamNames() {
            return paramNames;
        }

        /**
         * 根据参数名映射获取参数值数组，当参数不存在时使用默认值
         */
        public Object[] getParamValues(Map<String, Object> paramMap) {
            List<Object> values = new ArrayList<>();
            for (String paramName : paramNames) {
                Object value = resolveValue(paramMap, paramName);

                // 如果参数值为null，尝试使用默认值
                if (value == null) {
                    String defaultValue = paramDefaults.get(paramName);
                    if (defaultValue != null) {
                        value = parseDefaultValue(defaultValue);
                    }
                }

                if (isArrayOrCollection(value)) {
                    values.addAll(flattenArrayOrCollection(value));
                } else {
                    values.add(value);
                }
            }
            return values.toArray();
        }


        /**
         * 解析参数值，支持嵌套属性访问（如 user.name）
         *
         * @param paramMap 参数映射
         * @param key 参数键名，支持点号分隔的嵌套属性
         * @return 解析后的参数值
         */
        private Object resolveValue(Map<String, Object> paramMap, String key) {
            if (key.contains(".")) {
                String[] parts = key.split("\\.", 2);
                Object obj = paramMap.get(parts[0]);
                if (obj instanceof Map) {
                    return ((Map<?, ?>) obj).get(parts[1]);
                } else if (obj != null) {
                    try {
                        return ReflectUtil.getFieldValue(obj, parts[1]);
                    } catch (NoSuchFieldException | InvocationTargetException | NoSuchMethodException e) {
                        throw new RuntimeException("无法获取属性: " + key, e);
                    }
                }
            } else {
                return paramMap.get(key);
            }
            return null;
        }
    }

    /**
     * 解析默认值字符串为相应的对象类型
     */
    private static Object parseDefaultValue(String defaultValue) {
        if (defaultValue == null || defaultValue.isEmpty()) {
            return null;
        }

        // 处理带引号的字符串（移除引号）
        if ((defaultValue.startsWith("'") && defaultValue.endsWith("'")) ||
                (defaultValue.startsWith("\"") && defaultValue.endsWith("\""))) {
            String strValue = defaultValue.substring(1, defaultValue.length() - 1);
            // 不对默认值中的单引号进行额外转义，在填充SQL时统一处理
            return strValue;
        }

        // 处理数组格式的默认值，如 [0] 或 [1,2,3]
        if (defaultValue.startsWith("[") && defaultValue.endsWith("]")) {
            String arrayContent = defaultValue.substring(1, defaultValue.length() - 1);
            if (arrayContent.trim().isEmpty()) {
                return new ArrayList<>();
            }

            List<Object> list = new ArrayList<>();
            String[] elements = arrayContent.split(",");
            for (String element : elements) {
                element = element.trim();
                // 递归解析数组元素
                list.add(parseDefaultValue(element));
            }
            return list;
        }

        // 处理数字类型
        try {
            if (defaultValue.contains(".")) {
                return Double.parseDouble(defaultValue);
            } else {
                return Long.parseLong(defaultValue);
            }
        } catch (NumberFormatException e) {
            // 不是数字，继续处理
        }

        // 处理布尔类型
        if ("true".equalsIgnoreCase(defaultValue)) {
            return true;
        }
        if ("false".equalsIgnoreCase(defaultValue)) {
            return false;
        }

        // 处理空值
        if ("null".equalsIgnoreCase(defaultValue)) {
            return null;
        }

        // 默认为字符串
        return defaultValue;
    }

    /**
     * 将包含#{paramName}和${paramName}的SQL转换为?占位符的JDBC预编译SQL
     * 其中#{paramName}会被转换为?占位符，${paramName}会被直接替换为对应的值
     * 特别处理数组和集合类型的参数，生成多个?占位符
     *
     * @param sql 原始SQL，包含#{paramName}和${paramName}格式的参数
     * @param paramMap 参数映射表
     * @return 转换后的预编译SQL，参数替换为?占位符
     */
    public static String convertToPreparedStatement(String sql, Map<String, Object> paramMap) {
        if (sql == null || sql.isEmpty()) {
            return sql;
        }

        // 先处理${}参数，直接替换为对应的值
        String processedSql = processDollarParameters(sql, paramMap);

        // 再处理#{}参数，转换为?占位符
        StringBuffer result = new StringBuffer();
        Matcher matcher = PARAM_PATTERN.matcher(processedSql);

        while (matcher.find()) {
            String paramName = matcher.group(1);
            String defaultValue = matcher.group(2);

            Object value = resolveSimpleValue(paramMap, paramName);

            // 如果值为null且有默认值，则使用默认值
            if (value == null && defaultValue != null) {
                value = parseDefaultValue(defaultValue);
            }

            if (isArrayOrCollection(value)) {
                int size = getCollectionSize(value);
                if (size > 0) {
                    StringBuilder placeholders = new StringBuilder();
                    for (int i = 0; i < size; i++) {
                        if (i > 0) placeholders.append(",");
                        placeholders.append("?");
                    }
                    matcher.appendReplacement(result, placeholders.toString());
                } else {
                    matcher.appendReplacement(result, "NULL");
                }
            } else {
                matcher.appendReplacement(result, "?");
            }
        }
        matcher.appendTail(result);
        return result.toString();
    }


    /**
     * 处理${}格式的参数，直接替换为对应的值
     * 注意：这种替换方式可能导致SQL注入，使用时需确保参数安全
     *
     * @param sql 原始SQL
     * @param paramMap 参数映射表
     * @return 处理后的SQL
     */
    private static String processDollarParameters(String sql, Map<String, Object> paramMap) {
        if (sql == null || sql.isEmpty()) {
            return sql;
        }
        // 如果paramMap为null，创建一个空的Map，确保后续处理不会出现空指针
        if (paramMap == null) {
            paramMap = new HashMap<>();
        }

        StringBuffer result = new StringBuffer();
        Matcher matcher = DOLLAR_PARAM_PATTERN.matcher(sql);

        while (matcher.find()) {
            String paramName = matcher.group(1);
            String defaultValue = matcher.group(2);
            Object value = resolveSimpleValue(paramMap, paramName);

            // 如果参数值为null，则使用默认值
            if (value == null) {
                value = defaultValue;
            }

            String replacement;
            if (value == null) {
                replacement = "";
            } else if (isArrayOrCollection(value)) {
                replacement = formatArrayOrCollectionForDollar(value);
            } else {
                replacement = escapeSqlString(value.toString());
            }

            matcher.appendReplacement(result, replacement);
        }
        matcher.appendTail(result);
        return result.toString();
    }


    /**
     * 简化版本的参数解析，不处理嵌套
     *
     * @param paramMap 参数映射表
     * @param key 参数键名
     * @return 参数值
     */
    private static Object resolveSimpleValue(Map<String, Object> paramMap, String key) {
        if (key.contains(".")) {
            String[] parts = key.split("\\.", 2);
            Object obj = paramMap.get(parts[0]);
            if (obj instanceof Map) {
                return ((Map<?, ?>) obj).get(parts[1]);
            }
        }
        return paramMap.get(key);
    }

    /**
     * 提取SQL中#{}格式的参数名和默认值
     */
    private static Map<String, String> extractParamDefaults(String sql) {
        Map<String, String> paramDefaults = new HashMap<>();
        Matcher matcher = PARAM_PATTERN.matcher(sql);
        while (matcher.find()) {
            String paramName = matcher.group(1);
            String defaultValue = matcher.group(2);
            paramDefaults.put(paramName, defaultValue);
        }
        return paramDefaults;
    }

    /**
     * 提取SQL中#{}格式的参数名
     *
     * @param sql 原始SQL
     * @return 参数名列表
     */
    private static List<String> extractParamNames(String sql) {
        List<String> paramNames = new ArrayList<>();
        Matcher matcher = PARAM_PATTERN.matcher(sql);
        while (matcher.find()) {
            paramNames.add(matcher.group(1));
        }
        return paramNames;
    }

    /**
     * 将SQL转换为预编译语句并提取参数
     * 先处理${}参数，再处理#{}参数
     *
     * @param sql 原始SQL
     * @param paramMap 参数映射表
     * @return SqlParams对象，包含预编译SQL和参数名列表
     */
    public static SqlParams parseSql(String sql, Map<String, Object> paramMap) {
        String preparedSql = convertToPreparedStatement(sql, paramMap);
        List<String> paramNames = extractParamNames(sql);
        Map<String, String> paramDefaults = extractParamDefaults(sql);
        return new SqlParams(preparedSql, paramNames, paramDefaults);
    }


    /**
     * 保持向后兼容的版本
     *
     * @param sql 原始SQL
     * @return SqlParams对象，包含预编译SQL和参数名列表
     */
    public static SqlParams parseSql(String sql) {
        return parseSql(sql, new HashMap<>());
    }

    // === XSS 过滤相关方法 ===

    /**
     * 过滤 XSS
     *
     * @param input 原始输入字符串
     * @return 过滤后的安全字符串
     */
    public static String sanitizeForXSS(String input) {
        if (input == null || input.isEmpty()) {
            return input;
        }

        String escaped = input.replaceAll("[\\p{Cc}\\p{Cf}\\p{Co}\\p{Cn}]", "");
        escaped = StringEscapeUtils.escapeHtml4(escaped);
        escaped = escaped
            .replaceAll("(?i)javascript:", "javascript&#58;")
            .replaceAll("(?i)data:", "data&#58;")
            .replaceAll("(?i)vbscript:", "vbscript&#58;");
        escaped = escaped.replace("\"", "&#34;");
        return escaped;
    }

    /**
     * 消除XSS漏洞
     *
     * @param paramMap 参数
     * @return 参数
     */
    public static Map<String, Object> sanitizeParamMap(Map<String, Object> paramMap) {
        if (paramMap == null || paramMap.isEmpty()) {
            return paramMap;
        }

        Map<String, Object> sanitizedMap = new HashMap<>();
        for (Map.Entry<String, Object> entry : paramMap.entrySet()) {
            Object value = entry.getValue();
            sanitizedMap.put(entry.getKey(), sanitizeValue(value));
        }
        return sanitizedMap;
    }

    /**
     * 对值进行XSS过滤处理
     *
     * @param value 原始值
     * @return 过滤后的值
     */
    private static Object sanitizeValue(Object value) {
        if (value instanceof String) {
            return sanitizeForXSS((String) value);
        } else if (value instanceof Map) {
            return sanitizeParamMap((Map<String, Object>) value);
        } else if (value instanceof List) {
            List<Object> sanitizedList = new ArrayList<>();
            for (Object item : (List<?>) value) {
                sanitizedList.add(sanitizeValue(item));
            }
            return sanitizedList;
        } else if (value instanceof Object[]) {
            Object[] array = (Object[]) value;
            List<Object> sanitizedList = new ArrayList<>();
            for (Object item : array) {
                sanitizedList.add(sanitizeValue(item));
            }
            return sanitizedList.toArray();
        } else {
            return value;
        }
    }

    // === SQL 拼接相关方法 ===

    /**
     * 转义SQL字符串中的单引号
     *
     * @param input 原始字符串
     * @return 转义后的字符串
     */
    private static String escapeSqlString(String input) {
        if (input == null) {
            return null;
        }
        return input.replace("'", "''");
    }

    /**
     * 填充参数
     * 同时处理#{}和${}格式的参数
     *
     * @param sql 预编译SQL
     * @param paramMap 参数映射表
     * @return 填充后的SQL
     */
    public static String fillParameters(String sql, Map<String, Object> paramMap) {
        if (sql == null || sql.isEmpty()) {
            return sql;
        }
        // 如果paramMap为null，创建一个空的Map，确保后续处理不会出现空指针
        if (paramMap == null) {
            paramMap = new HashMap<>();
        }

        paramMap = sanitizeParamMap(paramMap);

        // 先处理${}参数
        String processedSql = processDollarParameters(sql, paramMap);

        // 再处理#{}参数
        StringBuffer result = new StringBuffer();
        Matcher matcher = PARAM_PATTERN.matcher(processedSql);

        while (matcher.find()) {
            String paramName = matcher.group(1);
            String defaultValue = matcher.group(2);
            Object value = resolveValue(paramMap, paramName);

            // 如果值为null且有默认值，则使用默认值
            if (value == null && defaultValue != null) {
                value = parseDefaultValue(defaultValue);
            }

            String replacement;
            if (value == null) {
                replacement = "NULL";
            } else if (isArrayOrCollection(value)) {
                replacement = formatArrayOrCollection(value);
            } else if (value instanceof String) {
                replacement = "'" + escapeSqlString((String) value) + "'";
            } else if (value instanceof Number || value instanceof Boolean) {
                replacement = value.toString();
            } else if (value instanceof Date) {
                Timestamp timestamp = new Timestamp(((Date) value).getTime());
                replacement = "'" + timestamp + "'";
            } else if (value instanceof Enum) {
                replacement = "'" + escapeSqlString(((Enum<?>) value).name()) + "'";
            } else {
                replacement = "'" + escapeSqlString(value.toString()) + "'";
            }

            matcher.appendReplacement(result, replacement);
        }
        matcher.appendTail(result);
        return result.toString();
    }

    /**
     * 解析参数值，支持嵌套属性访问（如 user.name）
     *
     * @param paramMap 参数映射
     * @param key 参数键名，支持点号分隔的嵌套属性
     * @return 解析后的参数值
     */
    private static Object resolveValue(Map<String, Object> paramMap, String key) {
        if (key.contains(".")) {
            String[] parts = key.split("\\.", 2);
            Object obj = paramMap.get(parts[0]);
            if (obj instanceof Map) {
                return ((Map<?, ?>) obj).get(parts[1]);
            } else if (obj != null) {
                try {
                    return ReflectUtil.getFieldValue(obj, parts[1]);
                } catch (Exception e) {
                    throw new RuntimeException("无法获取属性: " + key, e);
                }
            }
        } else {
            return paramMap.get(key);
        }
        return null;
    }

    // === 数组和集合处理方法 ===

    /**
     * 判断对象是否为数组或集合类型
     *
     * @param obj 待判断的对象
     * @return 如果是数组或集合类型返回true，否则返回false
     */
    private static boolean isArrayOrCollection(Object obj) {
        return obj != null && (obj.getClass().isArray() || obj instanceof Collection);
    }

    /**
     * 获取数组或集合的大小
     *
     * @param obj 数组或集合对象
     * @return 对象包含的元素数量
     */
    private static int getCollectionSize(Object obj) {
        if (obj == null) return 0;
        if (obj.getClass().isArray()) {
            return java.lang.reflect.Array.getLength(obj);
        } else if (obj instanceof Collection) {
            return ((Collection<?>) obj).size();
        }
        return 0;
    }

    /**
     * 将数组或集合展开为列表
     *
     * @param obj 数组或集合对象
     * @return 包含所有元素的列表
     */
    private static List<Object> flattenArrayOrCollection(Object obj) {
        List<Object> result = new ArrayList<>();
        if (obj == null) return result;

        if (obj.getClass().isArray()) {
            int length = java.lang.reflect.Array.getLength(obj);
            for (int i = 0; i < length; i++) {
                result.add(java.lang.reflect.Array.get(obj, i));
            }
        } else if (obj instanceof Collection) {
            result.addAll((Collection<?>) obj);
        }
        return result;
    }

    /**
     * 格式化数组或集合作为SQL中的IN子句参数
     *
     * @param obj 数组或集合对象
     * @return 格式化后的字符串，如 ('a','b','c')
     */
    private static String formatArrayOrCollection(Object obj) {
        if (obj == null) return "NULL";

        List<Object> items = flattenArrayOrCollection(obj);
        if (items.isEmpty()) return "NULL";

        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (int i = 0; i < items.size(); i++) {
            if (i > 0) sb.append(",");

            Object item = items.get(i);
            if (item instanceof String) {
                sb.append("'").append(escapeSqlString((String) item)).append("'");
            } else if (item instanceof Number || item instanceof Boolean) {
                sb.append(item);
            } else if (item instanceof Date) {
                Timestamp timestamp = new Timestamp(((Date) item).getTime());
                sb.append("'").append(timestamp).append("'");
            } else if (item instanceof Enum) {
                sb.append("'").append(escapeSqlString(((Enum<?>) item).name())).append("'");
            } else {
                sb.append("'").append(escapeSqlString(item.toString())).append("'");
            }
        }
        sb.append(")");
        return sb.toString();
    }

    /**
     * 为${}参数格式化数组或集合
     * 直接拼接元素，不添加括号，用于诸如IN条件等场景
     *
     * @param obj 数组或集合对象
     * @return 格式化后的字符串，如 'a','b','c'
     */
    private static String formatArrayOrCollectionForDollar(Object obj) {
        if (obj == null) return "";

        List<Object> items = flattenArrayOrCollection(obj);
        if (items.isEmpty()) return "";

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < items.size(); i++) {
            if (i > 0) sb.append(",");

            Object item = items.get(i);
            if (item instanceof String) {
                sb.append("'").append(escapeSqlString((String) item)).append("'");
            } else if (item instanceof Number || item instanceof Boolean) {
                sb.append(item);
            } else if (item instanceof Date) {
                Timestamp timestamp = new Timestamp(((Date) item).getTime());
                sb.append("'").append(timestamp).append("'");
            } else if (item instanceof Enum) {
                sb.append("'").append(escapeSqlString(((Enum<?>) item).name())).append("'");
            } else {
                sb.append("'").append(escapeSqlString(item.toString())).append("'");
            }
        }
        return sb.toString();
    }


    /**
     * 测试主方法
     */
    public static void main(String[] args) {
        try {
            // 创建完整测试参数
            Map<String, Object> fullParamMap = new HashMap<>();
            fullParamMap.put("tableName", "users");
            fullParamMap.put("id", 1001);
            fullParamMap.put("name", "张三' OR '1'='1"); // 包含SQL注入尝试
            fullParamMap.put("status", Arrays.asList(1, 2, 3));
            fullParamMap.put("createTime", new Date());
            fullParamMap.put("score", new BigDecimal("95.5"));
            fullParamMap.put("isVip", true);

            // 嵌套对象测试
            Map<String, Object> user = new HashMap<>();
            user.put("age", 25);
            user.put("hobbies", new String[]{"reading", "sports"});
            fullParamMap.put("user", user);

            // 创建部分参数（用于测试默认值）
            Map<String, Object> partialParamMap = new HashMap<>();
            partialParamMap.put("id", 2001);
            partialParamMap.put("isVip", false);

            // 测试SQL - 包含各种参数类型和默认值场景
            String sql =
                "SELECT ${columns:id, name, age} FROM ${tableName:default_table} " +
                "WHERE 1=1 " +
                "AND id = #{id:999} " +
                "AND name = #{name:'默认名称'} " +
                "AND status IN (#{status:[0, 'default']}) " +
                "AND create_time > #{createTime:null} " +
                "AND score > #{score:60.0} " +
                "AND age > #{user.age:18} " +
                "AND hobbies IN (#{user.hobbies:['coding']}) " +
                "AND is_vip = #{isVip:false} " +
                "AND level = #{level:3} " +
                "ORDER BY ${orderBy:create_time} " +
                "LIMIT #{limit:10}";

            System.out.println("=== 1. 原始SQL ===");
            System.out.println(sql);
            System.out.println();

            // 测试转换为预编译SQL
            String preparedSql = convertToPreparedStatement(sql, fullParamMap);
            System.out.println("=== 2. 转换后的预编译SQL（完整参数） ===");
            System.out.println(preparedSql);
            System.out.println();

            // 使用部分参数转换
            String partialPreparedSql = convertToPreparedStatement(sql, partialParamMap);
            System.out.println("=== 3. 转换后的预编译SQL（部分参数） ===");
            System.out.println(partialPreparedSql);
            System.out.println();

            // 测试解析SQL参数
            SqlParams sqlParams = parseSql(sql, fullParamMap);
            System.out.println("=== 4. 解析的参数名列表 ===");
            System.out.println(sqlParams.getParamNames());
            System.out.println();

            // 测试完整参数值数组
            Object[] fullParamValues = sqlParams.getParamValues(fullParamMap);
            System.out.println("=== 5. 完整参数值数组 ===");
            for (int i = 0; i < fullParamValues.length; i++) {
                System.out.printf("参数 %d: %s (%s)%n",
                    i,
                    fullParamValues[i],
                    (fullParamValues[i] != null ? fullParamValues[i].getClass().getSimpleName() : "null")
                );
            }
            System.out.println();

            // 测试部分参数值数组（使用默认值）
            Object[] partialParamValues = sqlParams.getParamValues(partialParamMap);
            System.out.println("=== 6. 部分参数值数组（使用默认值） ===");
            for (int i = 0; i < partialParamValues.length; i++) {
                System.out.printf("参数 %d: %s (%s)%n",
                    i,
                    partialParamValues[i],
                    (partialParamValues[i] != null ? partialParamValues[i].getClass().getSimpleName() : "null")
                );
            }
            System.out.println();

            // 测试填充完整参数后的SQL
            String fullFilledSql = fillParameters(sql, fullParamMap);
            System.out.println("=== 7. 填充完整参数后的SQL ===");
            System.out.println(fullFilledSql);
            System.out.println();

            // 测试填充部分参数后的SQL
            String partialFilledSql = fillParameters(sql, partialParamMap);
            System.out.println("=== 8. 填充部分参数后的SQL ===");
            System.out.println(partialFilledSql);
            System.out.println();

            // 测试特殊字符处理
            Map<String, Object> specialCharMap = new HashMap<>();
            specialCharMap.put("name", "O'Neil");
            specialCharMap.put("search", "%_\\");
            String specialSql = "SELECT * FROM users WHERE name = #{name} AND keyword LIKE #{search:'%'}";
            String specialFilledSql = fillParameters(specialSql, specialCharMap);
            System.out.println("=== 9. 特殊字符处理测试 ===");
            System.out.println("原始SQL: " + specialSql);
            System.out.println("处理后: " + specialFilledSql);
            System.out.println();

            // 测试XSS过滤
            String xssInput = "<script>alert('xss')</script>";
            String sanitizedXss = sanitizeForXSS(xssInput);
            System.out.println("=== 10. XSS过滤测试 ===");
            System.out.println("原始输入: " + xssInput);
            System.out.println("过滤后: " + sanitizedXss);
            System.out.println();

            // 测试空参数处理
            Map<String, Object> emptyParamMap = new HashMap<>();
            String emptyFilledSql = fillParameters(sql, emptyParamMap);
            System.out.println("=== 11. 空参数处理测试（全部使用默认值） ===");
            System.out.println(emptyFilledSql);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
